package org.darkbits.cyklon.engine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.darkbits.cyklon.machine.Input;
import org.darkbits.cyklon.machine.Output;
import org.darkbits.cyklon.machine.ProcessException;
import org.darkbits.cyklon.machine.Processable;

public class Engine {
	List<ConnectedMachine> machines;
	List<Connector> connectors;
	List<Processable> schedule;
	ConnectedMachine root;
		
	public Engine() {
		machines = Collections.synchronizedList(new ArrayList<ConnectedMachine>());
		connectors = Collections.synchronizedList(new ArrayList<Connector>());
		schedule = new ArrayList<Processable>();
		
		/* TODO: Add a master
		 * root = new MasterMachine()
		 * machines.add(root);
		 */ 
	}
	
	/**
	 * Helper for rebuildSchedule. Iterates over the machines
	 * and connector depth first and adds them to the schedule.
	 * 
	 * @param m The machine to start from
	 */
	private void buildScheduleFrom(ConnectedMachine m) {		
		for (Connector c : m.getInputConnectors()) {						
			if (!schedule.contains(c.getInputMachine())) {
				buildScheduleFrom(c.getInputMachine());
			}
			schedule.add(0, c);
		}
		
		schedule.add(m);
	}
	
	private synchronized void rebuildSchedule() {
		schedule.clear();	
		buildScheduleFrom(root);
	}

	public List<Connector> getConnectors() {
		return Collections.unmodifiableList(connectors);
	}

	public List<ConnectedMachine> getMachines() {
		return Collections.unmodifiableList(machines);
	}
	
	public void addMachine(ConnectedMachine machine) {
		machines.add(machine);
	}
	
	private boolean createsCycle(ConnectedMachine input, ConnectedMachine output) {
		Queue<ConnectedMachine> queue = new LinkedList<ConnectedMachine>();
		
		queue.add(input);
		while (queue.size() != 0) {
			ConnectedMachine m = queue.remove();
			
			if (m == output) {
				return true;
			}

			for (Connector c : m.getOutputConnectors()) {
				queue.add(c.getInputMachine());
			}
		}
		
		return false;
	}
	
	public void connect(Input inputPort, Output outputPort, ConnectedMachine inputMachine, ConnectedMachine outputMachine) {
		if (createsCycle(inputMachine, outputMachine)) {
			// TODO: Error message
			System.out.println("Error: Cycle!");
			return;
		}
		
		connectors.add(new Connector(inputPort, outputPort, inputMachine, outputMachine));
		rebuildSchedule();
	}
	
	public void removeConnector(Connector connector) {
		connector.breakConnection();
		connectors.remove(connector);
	}
	
	public void removeMachine(ConnectedMachine machine) {
		for (Connector c : machine.getInputConnectors()) {
			removeConnector(c);
		}
		
		for (Connector c : machine.getOutputConnectors()) {
			removeConnector(c);
		}
		
		machines.remove(machine);
	}
	
	public synchronized void process(int samples) {
		for (Processable p : schedule) {
			try {
				p.process(samples);
			} catch (ProcessException e) {
				// TODO: Error message				
				if (p instanceof ConnectedMachine) {
					((ConnectedMachine)p).setEnabled(false);
				} else if (p instanceof Connector) {
					removeConnector((Connector)p);
				}
			}
		}
	}
}
